#include "mempool.h"

#include <assert.h>
#include <malloc.h>


C_CODE_BEGIN

struct block {
	struct block* next;
};

struct freelist {
	struct freelist* next;
};


struct mempool {
	size_t isize;
	size_t objsize;
	size_t usize;
	size_t rsize;
	size_t align;
	struct freelist* flist;
	struct block* estart;
	struct block* eend;
};

/*
 * alignment macro which make the input x to the nearest alignment value
 */
#define ALIGN(x,align) \
	(((x) + (align)-1) & ~((align)-1))


#ifndef NDEBUG
/* in the debug version , I define another tag for the object in order to identify whether the memory
 * is valid .
 */
#define USED_MASK_VALUE ((void*)(0xcccceeee))
#define FREELIST_SIZE(objsize,align) \
	(ALIGN(objsize,align)+(sizeof(void*)))

#define MARK_FREELIST(ptr) \
	(*((void**)(ptr)) = USED_MASK_VALUE)

#define GET_FREELIST_RETURN(ptr) \
	((char*)(ptr) + sizeof(void*))

#define CHECK_FREELIST_PTR(ptr) \
	assert( *((void**)((char*)(ptr)-sizeof(void*))) == USED_MASK_VALUE )

#define GET_RAW_FREELIST_PTR(ptr,c) \
	((c)((char*)(ptr) - sizeof(void*)))

#else

#define FREELIST_SIZE(objsize) \
	ALIGN(objsize)
#define MARK_FREELIST(ptr) 
#define GET_FREELIST_RETURN(ptr) \
	(ptr)
#define CHECK_FREELIST_PTR(ptr)
#define GET_RAW_FREELIST_PTR(ptr,c) \
	((c)(ptr))
#endif // NDEBUG

	

static void _refill( struct mempool* mp , size_t pool_size ) {
	void* block_buffer;
	struct freelist* cur , *prev;
	size_t i ;
	size_t block_number=pool_size;
	assert( mp->eend == NULL || mp->eend->next == NULL );
	pool_size = pool_size >= 1 ? pool_size : 1;
	pool_size = pool_size * FREELIST_SIZE(mp->objsize,mp->align);
	block_buffer = malloc( pool_size + sizeof(struct block));
	if( mp->eend == NULL ) {
		mp->estart = (struct block*)(block_buffer);
		mp->eend = (struct block*)(block_buffer);
	} else {
		mp->eend->next = (struct block*)(block_buffer);
		mp->eend = (struct block*)(block_buffer);
	}
	mp->eend->next = NULL;
	/* building the free list from this buffer */
	prev = cur = (struct freelist*)( (char*)block_buffer + sizeof(struct block) );
	for( i =1 ; i < block_number ; ++i ) {
		cur = (struct freelist*)( (char*)(cur) + FREELIST_SIZE(mp->objsize,mp->align) );
		prev->next = cur;
		prev = cur;
	}
	cur->next = NULL;
	/* link the free list to the original free list */
	if( mp->flist == NULL ) {
		mp->flist = (struct freelist*)( (char*)block_buffer + sizeof(struct block) );
	} else {
		cur->next = mp->flist;
		mp->flist = (struct freelist*)( (char*)block_buffer + sizeof(struct block) );
	}

	mp->rsize += block_number;
}

static void _destroy( struct mempool* mp ) {
	struct block* tp = mp->estart;
	while( tp ) {
		mp->estart = mp->estart->next;
		free(tp);
		tp = mp->estart;
	}
}

EXPORT_FUNC void* mempool_fetch ( struct mempool* mp ) {
	void* ret;
	if(mp->usize == mp->rsize) {
		assert( mp->flist == NULL );
		_refill(mp,mp->isize);
	}
	assert( mp->flist != NULL );
	ret = mp->flist;
	mp->flist = ((struct freelist*)ret)->next;
	++(mp->usize);
	MARK_FREELIST(ret);
	return GET_FREELIST_RETURN(ret);

}

EXPORT_FUNC void mempool_return( struct mempool* mp , void* mem ) {
	void* rp;
	CHECK_FREELIST_PTR(mem);
	rp = GET_RAW_FREELIST_PTR(mem,void*);
	if( mp->flist == NULL ) {
		mp->flist = (struct freelist*)rp;
		mp->flist->next = NULL;
	} else {
		((struct freelist*)(rp))->next = mp->flist;
		mp->flist = (struct freelist*)rp;
	}
	--(mp->usize);
}


EXPORT_FUNC void mempool_destroy( struct mempool* mp ) {
	_destroy(mp);
	free(mp);
}

EXPORT_FUNC void mempool_clear( struct mempool* mp ) {
	_destroy(mp);

	mp->eend = mp->estart = NULL;
	mp->isize = mp->usize = 0;
	mp->flist = NULL;
}

EXPORT_FUNC void mempool_reserve( struct mempool* mp , size_t size ) {
	_refill(mp,size);
}

EXPORT_FUNC size_t mempool_get_usesize( struct mempool* mp ){
	return mp->usize;
}

EXPORT_FUNC size_t mempool_get_freesize( struct mempool* mp ) {
	return mp->rsize;
}


EXPORT_FUNC struct mempool* mempool_create( size_t alignsize , size_t objszie , size_t basesize ) {
	struct mempool* mp = malloc( sizeof(struct mempool) );
	mp->align = ALIGN(alignsize,4);
	mp->objsize = objszie;
	mp->eend = mp->estart = NULL;
	mp->flist = NULL;
	mp->rsize = 0;
	mp->usize = 0;
	mp->isize = basesize;
	mempool_reserve(mp,basesize);
	return mp;
}

C_CODE_END